﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

namespace Ragim.Selfpub.CrmSolution.Plugins.Tools
{
    class StringAttributeMap
    {
        public string opportunity { get; set; }
        public string contact { get; set; }
    }

    public class AttributesSynchronizer
    {
        public static string contact = "contact";
        public static string opportunity = "opportunity";

        private object lockObject = new object();
        private static object staticLock = new object();

        private IOrganizationService crmService;
        protected IOrganizationService CrmService
        {
            get
            {
                lock (lockObject)
                {
                    return crmService;
                }
            }

            set
            {
                crmService = value;
            }
        }

        private IOrganizationService adminCrmService;
        protected IOrganizationService AdminCrmService
        {
            get
            {
                lock (lockObject)
                {
                    return adminCrmService;
                }
            }

            set
            {
                adminCrmService = value;
            }
        }

        private StringAttributeMap[] entityFields = new StringAttributeMap[] 
        { 
            new StringAttributeMap() { opportunity = "new_phone_number",       contact = "mobilephone" },
            new StringAttributeMap() { opportunity = "new_city",               contact = "address1_city" },
            new StringAttributeMap() { opportunity = "new_country",            contact = "address1_country" },
            new StringAttributeMap() { opportunity = "new_region",             contact = "address1_stateorprovince" },
            new StringAttributeMap() { opportunity = "new_birthday",           contact = "birthdate" },
            new StringAttributeMap() { opportunity = "new_kind_of_activity",   contact = "new_kind_of_activity" },
        };

        public AttributesSynchronizer(IOrganizationService crmService, IOrganizationService adminService)
        {
            this.crmService = crmService;
            this.adminCrmService = adminService;
        }

        private List<StringAttributeMap> GetChangedFields(Entity before, Entity after)
        {
            List<StringAttributeMap> changedFields = new List<StringAttributeMap>();

            for (int i = 0; i < entityFields.Length; i++)
            {
                StringAttributeMap attribute = entityFields[i];

                string attributeName = GetAttributeNameForEntity(attribute, after);
                if (String.IsNullOrEmpty(attributeName)) continue;

                bool isFieldChanged = Util.ChangedAllTypesAttributeValue(before, after, attributeName);

                if (isFieldChanged)
                    changedFields.Add(attribute);
            }

            return changedFields;
        }

        private string GetAttributeNameForEntity(StringAttributeMap attribute, Entity entity)
        {
            string field = String.Empty;

            switch (entity.LogicalName)
            {
                case "contact":
                    field = attribute.contact;
                    break;
                case "opportunity":
                    field = attribute.opportunity;
                    break;
            }

            return field;
        }

        private string[] GetColumnsForEntity(string entityName)
        {
            List<string> columns = new List<string>();

            for (int i = 0; i < entityFields.Length; i++)
            {
                StringAttributeMap attribute = entityFields[i];

                string column = GetAttributeNameForEntity(attribute, new Entity(entityName));
                columns.Add(column);
            }

            return columns.ToArray();
        }

        public void Synchronize(Entity before, Entity after)
        {
            lock (staticLock)
            {
                List<StringAttributeMap> fieldsToSynchronize = new List<StringAttributeMap>();
                switch (after.LogicalName)
                {
                    case "opportunity":
                        ProcessOpportunity(before, after);
                        break;
                    case "contact":
                        ProcessContract(before, after);
                        break;
                }
            }
        }

        private void ProcessOpportunity(Entity before, Entity after)
        {
            List<StringAttributeMap> fieldsToSynchronize = GetChangedFields(before, after);
            if (fieldsToSynchronize.Count > 0)
            {
                var _fields = fieldsToSynchronize.Where(x => !string.IsNullOrEmpty(x.contact)).ToList();
                var customerRef = after.GetEntityReferenceAttributeValue("customerid");
                if (customerRef != null && customerRef.LogicalName == "contact")
                {
                    Entity contact = crmService.Retrieve_Custom(customerRef, new ColumnSet(_fields.Select(x => x.contact).ToArray()));

                    Entity forUpdate = new Entity("contact") { Id = contact.Id };

                    foreach (StringAttributeMap attribute in _fields)
                    {
                        if (Util.EqualsAllTypes(contact.GetAttribute(attribute.contact), after.GetAttribute(attribute.opportunity)) == false)
                            forUpdate[attribute.contact] = after[attribute.opportunity];
                    }

                    if (forUpdate.Attributes.Count > 0)
                        crmService.Update(forUpdate);
                }
            }
        }

        private void ProcessContract(Entity before, Entity after)
        {
            string step = "";
            try
            {
                step = "1";
                List<StringAttributeMap> fieldsToSynchronize = GetChangedFields(before, after);
                step = "2";
                if (fieldsToSynchronize.Count > 0)
                {
                    step = "3";
                    var _fields = fieldsToSynchronize.Where(x => !string.IsNullOrEmpty(x.opportunity)).ToList();

                    step = "4";
                    var opportunities = this.crmService.QueryByAttribute("opportunity",
                        new ColumnSet(_fields.Select(x => x.opportunity).ToArray()),
                        new string[1] { "customerid" }, new object[1] { after.Id }, null);

                    step = "5";
                    foreach (var opportunity in opportunities)
                    {
                        Entity forUpdate = new Entity("opportunity") { Id = opportunity.Id };

                        step = "6";
                        foreach (StringAttributeMap attribute in _fields)
                        {
                            step = "7";
                            if (Util.EqualsAllTypes(opportunity.GetAttribute(attribute.opportunity), after.GetAttribute(attribute.contact)) == false)
                            {
                                step = "8";
                                forUpdate[attribute.opportunity] = after[attribute.contact];
                            }
                        }

                        step = "9";
                        if (forUpdate.Attributes.Count > 0)
                        {
                            step = "10";
                            crmService.Update(forUpdate);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException(OperationStatus.Failed, 
                    string.Format("Error on [step = '{0}']: {1}", step, ex.ToString()));
            }
        }
    }
}
